﻿#light

namespace CodeBlock.AMD64

open System
open System.Collections.Generic
open System.Linq

open Mono.Cecil

open CodeBlock
open CodeBlock.AMD64.TypeResolver

open RuntimeUtils

type MemoryLayoutAMD64(resolver: BaseAssemblyResolver) =
    let layout = Dictionary<string, TypeLayoutInfo>()
    
    member this.GetStackType (cts_type: TypeDefinition) =
        match cts_type.FullName with
        | "System.Int32" | "System.UInt32" -> I32
        | "System.Single" | "System.Double" -> Float
        | "System.Int64" | "System.UInt64" -> I64
        | "System.Byte" | "System.SByte" | "System.Int16" | "System.UInt16" -> I32
        | "System.Boolean" -> I32
        | "System.IntPtr" | "System.UIntPtr" -> Native
        | _ ->
            if (*cts_type.IsPointer then Reference
            elif*) cts_type.IsClass then Object
            elif cts_type.IsInterface then Object
            elif cts_type.IsValueType then Object
            elif cts_type.IsEnum then (if this.[cts_type].Size < 8u then I32 else I64)
            else failwith ("can't get size of type " + cts_type.FullName)
    
    member this.GetStackSize(cts_type: TypeDefinition) =
        if cts_type.IsValueType then
            align 8u this.[cts_type].Size
        else
            8u
    
    member this.Item (typedef: TypeDefinition) =
        let got, value = layout.TryGetValue(typedef.FullName)
        if got then value
        else
            let value = this.CalculateLayout(typedef)
            layout.Add(typedef.FullName, value)
            value
    
    member this.CalculateLayout (typedef: TypeDefinition) =
        if typedef.IsValueType then
            let fields = typedef.Fields.OfType<FieldDefinition>() |> List.of_seq
            let ifields = fields |> List.filter (fun f -> not f.IsStatic)
            
            let coreTypeLayout () =
                Map.of_list [ifields.Single().Name, 0u]
            
            match typedef.FullName with
            | "System.Int32" | "System.UInt32" ->
                {   Layout = coreTypeLayout();
                    Size = 4u; Align = 4u;  }
            | "System.SByte" | "System.Byte" | "System.Boolean" ->
                {   Layout = coreTypeLayout();
                    Size = 1u; Align = 1u;  }
            | "System.Int16" | "System.UInt16" | "System.Char" ->
                {   Layout = coreTypeLayout();
                    Size = 2u; Align = 2u;  }
            | "System.Int64" | "System.UInt64" ->
                {   Layout = coreTypeLayout();
                    Size = 8u; Align = 8u;  }
            | _ ->
                if ifields |> List.exists (fun f -> typedef.FullName = f.FieldType.FullName) then
                    raise(InvalidProgramException(sprintf "non built-in type %s is self-describing" typedef.FullName))
                
                (* if ifields |> List.exists (fun f -> f.FieldType :?> GenericParameter) then
                    raise(InvalidProgramException(sprintf "Type %s contains generic type fields so its layout can't be calculated." typedef.FullName)) *)
                
                if ifields.Length = 0 then
                    {   Layout = Map.empty;
                        Size = 0u; Align = 0u;  }
                else
                    let result, size, alignment =
                        ifields
                        // |> List.sort_by (fun f -> this.[f.FieldType :?> TypeDefinition].Align)
                        |> List.fold_left (fun (layout,pos,alignment) f -> 
                                        let flayout = this.[resolver.ResolveType f.FieldType]
                                        let pos = align flayout.Align pos
                                        Map.add f.Name pos layout,
                                        pos + flayout.Size,
                                        max alignment flayout.Align) (Map.empty, 0u, 1u)
                    
                    {   Layout = result;
                        Size = size; Align = alignment; }
        else
            raise(NotImplementedException(sprintf "layout of reference types: %s" typedef.FullName))